package com.distributed.logicserver;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.SocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import com.distributed.common.Address;
import com.distributed.common.Constant;
import com.distributed.common.EventHandler;
import com.distributed.common.User;
import com.distributed.common.message.AskMessage;
import com.distributed.common.message.BaseMessage;
import com.distributed.common.message.CancelMessage;
import com.distributed.common.message.CreateRoomMessage;
import com.distributed.common.message.EnterRoomMessage;
import com.distributed.common.message.ErrorMessage;
import com.distributed.common.message.PingMessage;
import com.distributed.common.message.PongMessage;
import com.distributed.common.message.QuitMessage;
import com.distributed.common.message.ReadyStartMessage;
import com.distributed.common.message.VerifyMessage;
import com.distributed.common.message.VerifyRespMessage;
import com.distributed.dispatchserver.DispatchServer;
import com.distributed.util.HashUtils;

public class LogicServer {
	static {
		PropertyConfigurator.configure("log4j.properties");
	}
	private static Logger logger = Logger.getLogger(DispatchServer.class);

	//private final static int LEASE = 3600 * 24;
	
	private static UserSet users = new UserSet();
	
	private static int port = 5234;
	
	private static List<Address> cache_address_list = null;
	private static List<Address> data_address_list = null;
	private static List<JedisPool> jedisCachePoolList = new ArrayList<JedisPool>();
	private static List<JedisPool> jedisDataPoolList = new ArrayList<JedisPool>();
	
	private static void initServer() {
		LogicConfiguration.load();
		String hostIp = LogicConfiguration.getHostIp();
		int mport = LogicConfiguration.getHostPort();
		if (mport != -1) {
			port = mport;
		}
		if (hostIp == null) {
			try {
				hostIp = InetAddress.getLocalHost().getHostAddress();
			} catch (UnknownHostException e) {
				e.printStackTrace();
			}
			logger.info("Host: " + hostIp + ":" + port);
		} else {
			logger.info("Host: " + hostIp + ":" + port);
		}

		logger.info("data server list:");
		data_address_list = LogicConfiguration.getDataServerList();
		if (data_address_list == null || data_address_list.size() == 0) {
			logger.warn("Data Address list is empty");
			return;
		}
		for (Address address : data_address_list) {
			logger.info(address.toString());
		}
		
		logger.info("Cache server list:");
		cache_address_list = LogicConfiguration.getDataServerList();
		if (cache_address_list == null || cache_address_list.size() == 0) {
			logger.warn("Cache Address list is empty");
			return;
		}
		for (Address address : cache_address_list) {
			logger.info(address.toString());
		}
	}

	private static void startServer() throws Exception {
		for(int i = 0; i < data_address_list.size(); i++) {
			JedisPool jedisPool = new JedisPool(data_address_list.get(i).getIp(),
					data_address_list.get(i).getPort());
			jedisDataPoolList.add(jedisPool);
		}
		for(int i = 0; i < cache_address_list.size(); i++) {
			JedisPool jedisPool = new JedisPool(cache_address_list.get(i).getIp(),
					cache_address_list.get(i).getPort());
			jedisCachePoolList.add(jedisPool);
		}
		
		SocketAcceptor acceptor = new NioSocketAcceptor();
		
		DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
		chain.addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));
		chain.addLast("logger", new LoggingFilter());
		
		LogicProtocolHandler logicProtocolHandler = new LogicProtocolHandler();
		logicProtocolHandler.setHandler(dispatchEventHandler);
		acceptor.setHandler(logicProtocolHandler);
		acceptor.setReuseAddress(true);
		acceptor.bind(new InetSocketAddress(port));

		logger.info("Listening on Port: " + port);

		while (true) {
			logger.info("R: "
					+ acceptor.getStatistics().getReadBytesThroughput()
					+ " W: "
					+ acceptor.getStatistics().getWrittenBytesThroughput());
			Thread.sleep(60000);
		}
	}

	public static void main(String[] args) throws Exception {
		logger.info("Logic Server");

		initServer();
		startServer();
	}

	private static EventHandler dispatchEventHandler = new EventHandler() {

		public String process(IoSession ioSession, String request) {
			BaseMessage baseMessage = BaseMessage.getFromString(request);
			switch(baseMessage.getId()) {
			case Constant.ASK_MESSAGE:
				return doAsk(AskMessage.getFromString(request));
			case Constant.CANCEL_MESSAGE:
				return doCancel(CancelMessage.getFromString(request));
			case Constant.CREATEROOM_MESSAGE:
				return doCreateRoom(CreateRoomMessage.getFromString(request));
			case Constant.ENTER_ROOM_MESSAGE:
				return doEnterRoom(EnterRoomMessage.getFromString(request));
			case Constant.READY_MESSAGE:
				return doReady(ReadyStartMessage.getFromString(request));
			case Constant.PING_MESSAGE:
				return doPing(PingMessage.getFromString(request));
			case Constant.QUIT_MESSAGE:
				return doQuit(QuitMessage.getFromString(request));
			case Constant.VERIFY_MESSAGE:
				return doVerify(ioSession, VerifyMessage.getFromString(request));
			default:
				return ErrorMessage.ERROR;
			}
		}

		public void closeSession(IoSession ioSession) {
			users.remove(ioSession);
		}
	};
	
	private static String doAsk(AskMessage askMessage) {
		return null;
	}
	
	private static String doCancel(CancelMessage cancelMessage) {
		return null;
	}
	
	private static String doCreateRoom(CreateRoomMessage createRoomMessage) {
		return null;
	}
	
	private static String doEnterRoom(EnterRoomMessage enterRoomMessage) {
		return null;
	}
	
	private static String doReady(ReadyStartMessage readyStartMessage) {
		return null;
	}
	
	private static String doPing(PingMessage pingMessage) {
		if (pingMessage == null) {
			return PongMessage.ERROR;
		}
		
		return new PongMessage().toString();
	}
	
	private static String doQuit(QuitMessage quitMessage) {
		return null;
	}
	
	private static String doVerify(IoSession ioSession, VerifyMessage verifyMessage) {
		if (verifyMessage == null) {
			return VerifyRespMessage.ERROR;
		}
		
		JedisPool jedisPool = jedisCachePoolList.get(HashUtils.ModHash(jedisDataPoolList.size(),
				verifyMessage.getUserName()));
		Jedis cacheJedis = jedisPool.getResource();
		Map<String, String> userInfo = cacheJedis.hgetAll(verifyMessage.getUserName());
		if (userInfo == null || userInfo.size() == 0) {
			return new VerifyRespMessage(0).toString();
		}
		if (verifyMessage.getPassword() == null ||
				(!verifyMessage.getPassword().equals(userInfo.get(Constant.DATA_PASSWORD)))) {
			return new VerifyRespMessage(0).toString();
		}
		if (!userInfo.get(Constant.DATA_PORT).equals(verifyMessage.getPort())) {
			return new VerifyRespMessage(0).toString();
		}
		if (!userInfo.get(Constant.DATA_IP).equals(verifyMessage.getIp())) {
			return new VerifyRespMessage(0).toString();
		}
		
		int timestamp = Integer.valueOf(userInfo.get(Constant.DATA_TIMESTAMP));
		int iPort = Integer.valueOf(userInfo.get(Constant.DATA_PORT));
		User user = new User(verifyMessage.getUserName(), 
				verifyMessage.getPassword(), iPort, timestamp);
		user.setIp(userInfo.get(Constant.DATA_IP));
		user.setVerify(1);
		
		users.addUser(verifyMessage.getUserName(), ioSession, user);
		
		jedisPool.returnResource(cacheJedis);
		
		return new VerifyRespMessage(1).toString();
	}
}
